<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { Cell, Graph, Node } from '@antv/x6'
import { GraphConfig, VueNodePorts } from './config/graph.config'
import { Dnd } from '@antv/x6-plugin-dnd'
import Guides from 'vue3-guides'
import { GuideXConfig, GuideYConfig } from './config/guide.config'
import Circle from './components/node/circle.vue'
import Rect from './components/node/rect.vue'
import { register } from '@antv/x6-vue-shape'
import { Snapline } from '@antv/x6-plugin-snapline'

let graph: Graph
let dnd: Dnd

const graphContainer = ref<HTMLDivElement | null>(null)
const dndContainer = ref<HTMLDivElement | null>(null)

const rulerZoom = ref(1)

register({
  shape: 'circle-vue-node',
  component: Circle,
  width: 100,
  height: 100,
  ports: VueNodePorts,
})

register({
  shape: 'rect-vue-node',
  component: Rect,
  width: 300,
  height: 300,
  ports: VueNodePorts,
})

const guideX = ref<any | null>(null)
const guideY = ref<any | null>(null)

const onResize = () => {
  guideX!.value.resize()
  guideY!.value.resize()
}

const startDrag = (event: MouseEvent) => {
  const target = event.currentTarget as HTMLElement
  const type = target.dataset.type

  let node: Node;
  if (type === 'copy') {
    node = getCopyNode();
  } else {
    const shape = type === 'rect' ? 'rect-vue-node' : 'circle-vue-node'
    node = graph.createNode({ shape })
  }
  dnd.start(node, event)
}

const scrollX = ref(0)
const scrollY = ref(0)
const copyNode = ref<Cell | null>(null)   // 可复制的节点

/**
 * 初始化拷贝节点
 */
const create = () => {
  const rectNode = graph.addNode({
    shape: 'rect-vue-node',
    x: 100,
    y: 100,
  })

  const circleNode = graph.addNode({
    shape: 'circle-vue-node',
    x: 200,
    y: 200,
  })

  rectNode.addChild(circleNode)
  copyNode.value = rectNode;
};

/**
 * 监听拖拽结束事件
 */
const handleDrop = () => {
  graph.on('node:added', ({ node, options }) => {  
    // 检查是否是DND触发的 
    // 是否存在子节点
    if (options?.stencil && node.prop('hasChildren')) {  
      const childrenData = node.prop('childrenData') as Cell.Properties[]  
      if (childrenData && childrenData.length > 0) {  
      childrenData.forEach((childData) => {  
        const childNode = graph.addNode({  
          ...childData,  
          id: undefined,  
          x: node.position().x + (childData.position?.x || 0),  
          y: node.position().y + (childData.position?.y || 0),  
        })  
          
        node.addChild(childNode)  
      })  
        
      // 清理临时数据  
      node.removeProp('hasChildren')  
      node.removeProp('childrenData')  
    }  
    } 
  })
}

onMounted(() => {
  graph = new Graph({
    container: graphContainer!.value!,
    ...GraphConfig,
  })

  graph.use(
    new Snapline({
      enabled: true,
    }),
  )

  dnd = new Dnd({
    target: graph,
    scaled: false,
    dndContainer: dndContainer!.value! 
  })

  window.addEventListener('resize', onResize)
  create()
  handleDrop()

  // 监听画布缩放
  graph.on('scale', ({ sx }) => {
    rulerZoom.value = sx
  })

  // 监听画图移动
  graph.on('translate', ({ tx, ty }) => {
    // 限制不能小于 0
    const newTx = Math.max(0, tx)
    const newTy = Math.max(0, ty)

    if (newTx !== tx || newTy !== ty) {
      // 立即修正画布位置
      graph.translate(newTx, newTy)
    }

    scrollX.value = newTx
    scrollY.value = newTy
  })
})

/**
 * 复制并存储节点
 */
const copy = () => {
  const node = copyNode.value!;
  const parent = node.toJSON();
  const children = node.getChildren()?.map(child => child.toJSON());
  window.localStorage.setItem('node', JSON.stringify({parent, children}));
}

/**
 * 获取存储节点信息，并初始化
 */
const getCopyNode = () => {
  const storage = JSON.parse(window.localStorage.getItem('node')!);
  const { parent, children } = storage as {parent: Cell.Properties, children: Cell.Properties[]};

  // 创建父节点，位置偏移以避免重叠
  const parentNode = graph.createNode({
    ...parent,

    // 让 X6 自动生成新的 ID
    id: undefined, 

    // 重置 position
    x: 0,
    y: 0,
    
    // 子节点本身不在画布模型中，导致拖拽结束后，父节点上没有 children 信息
    // 将 children 作为父节点的属性值进行传递
    hasChildren: !!children?.length,
    childrenData: children.map(child => ({  
      ...child,
      position: {  
        x: child.position?.x - parent.position?.x || 0,  
        y: child.position?.y - parent.position?.y || 0,  
      }  
    }))
  });
  return parentNode;
}
</script>

<template>
  <div id="app">
    <div id="dndContainer" ref="dndContainer">
      <button @click="copy">copy</button>
      <div 
        class="copy-node" 
        data-type="copy"
        @dragstart="startDrag" 
        draggable="true"
      >
        Copy Node
      </div>
      <Circle @dragstart="startDrag" />
      <Rect @dragstart="startDrag" />
    </div>
    <div id="aside">
      <div id="toolbar">toolbar</div>
      <div id="main">
        <div class="ruler horizontal">
          <Guides
            ref="guideX"
            v-bind="GuideXConfig"
            type="horizontal"
            :scrollPos="scrollX"
            :zoom="rulerZoom"
            v-bind:rulerStyle="{
              left: '30px',
              width: 'calc(100% - 30px)',
              height: '30px',
            }"
          />
        </div>
        <div class="ruler vertical">
          <Guides
            ref="guideY"
            v-bind="GuideYConfig"
            :scrollPos="scrollY"
            type="vertical"
            displayDragPos="true"
            :zoom="rulerZoom"
            v-bind:rulerStyle="{
              top: '30px',
              height: 'calc(100% - 30px)',
              width: '30px',
            }"
          />
        </div>
        <div id="graph-container">
          <div ref="graphContainer"></div>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
#app {
  height: 100%;
  width: 100%;
  display: flex;
  flex-wrap: nowrap;
}

#dndContainer {
  min-width: 300px;
}

#aside {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
}

#toolbar {
  height: 32px;
  line-height: 32px;
}

#main {
  position: relative;
  flex-grow: 1;
  display: flex;
  justify-content: space-between;
  width: 100%;
  height: 100%;
}

.ruler {
  position: absolute;
  top: 0;
  left: 0;
}

.ruler.horizontal {
  width: 100%;
  height: 30px;
}

.ruler.vertical {
  width: 30px;
  height: 100%;
}

#graph-container {
  position: relative;
  top: 30px;
  left: 30px;
  height: calc(100% - 30px);
  width: calc(100% - 30px);
}

.copy-node {
  margin: 10px;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
  cursor: move;
  background: #f5f5f5;
  text-align: center;
}

.copy-node:hover {
  background: #e6e6e6;
}
</style>
